/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2006
* Sleepycat Software. All rights reserved.
*
* $Id: ChildReference.java,v 1.1 2006/05/06 09:00:14 ckaestne Exp $
*/
package com.sleepycat.je.tree;
import java.nio.ByteBuffer;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.LogFileNotFoundException;
import com.sleepycat.je.log.LogReadable;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LogWritable;
import com.sleepycat.je.utilint.DbLsn;
/**
* A ChildReference is a reference in the tree from parent to child. It
* contains a node reference, key, and LSN.
*/
public class ChildReference implements LogWritable, LogReadable {
private Node target;
private long lsn;
private byte[] key;
/*
* The state byte holds knownDeleted state in bit 0 and dirty state in bit
* 1. Bit flags are used here because of the desire to keep the child
* reference compact. State is persistent because knownDeleted is
* persistent, but the dirty bit is cleared when read in from the log.
*
* -- KnownDeleted is a way of indicating that the reference is invalid
* without logging new data. This happens in aborts and recoveries. If
* knownDeleted is true, this entry is surely deleted. If knownDeleted is
* false, this entry may or may not be deleted. Future space optimizations:
* store as a separate bit array in the BIN, or subclass ChildReference and
* make a special reference only used by BINs and not by INs.
*
* -- Dirty is true if the LSN or key has been changed since the last time
* the ownign node was logged. This supports the calculation of BIN deltas.
*/
private byte state;
private static final byte KNOWN_DELETED_BIT = 0x1;
private static final byte DIRTY_BIT = 0x2;
private static final byte CLEAR_DIRTY_BIT = ~0x2;
private static final byte MIGRATE_BIT = 0x4;
private static final byte CLEAR_MIGRATE_BIT = ~0x4;
private static final byte PENDING_DELETED_BIT = 0x8;
/**
* Construct an empty child reference, for reading from the log.
*/
ChildReference() {
init(null, Key.EMPTY_KEY, DbLsn.NULL_LSN, 0);
}
/**
* Construct a ChildReference for inserting a new entry.
*/
public ChildReference(Node target, byte[] key, long lsn) {
init(target, key, lsn, DIRTY_BIT);
}
/**
* Construct a ChildReference for inserting an existing entry.
*/
public ChildReference(Node target,
byte[] key,
long lsn,
byte existingState) {
init(target, key, lsn, existingState | DIRTY_BIT);
}
private void init(Node target,
byte[] key,
long lsn,
int state) {
this.target = target;
this.key = key;
this.lsn = lsn;
this.state = (byte) state;
}
/**
* Return the key for this ChildReference.
*/
public byte[] getKey() {
return key;
}
/**
* Set the key for this ChildReference.
*/
public void setKey(byte[] key) {
this.key = key;
state |= DIRTY_BIT;
}
/**
* Fetch the target object that this ChildReference refers to. If the
* object is already in VM, then just return the reference to it. If the
* object is not in VM, then read the object from the log. If the object
* has been faulted in and the in arg is supplied, then the total memory
* size cache in the IN is invalidated.
*
* @param database The database that this ChildReference resides in.
* @param in The IN that this ChildReference lives in. If
* the target is fetched (i.e. it is null on entry), then the
* total in memory count is invalidated in the IN. May be null.
* For example, the root is a ChildReference and there is no parent IN
* when the rootIN is fetched in.
* @return the Node object representing the target node in the tree, or
* null if there is no target of this ChildReference, or null if a
* pendingDelete or knownDeleted entry has been cleaned.
*/
public Node fetchTarget(DatabaseImpl database, IN in)
throws DatabaseException {
if (target == null) {
/* fault object in from log */
if (lsn == DbLsn.NULL_LSN) {
if (!isKnownDeleted()) {
throw new DatabaseException(IN.makeFetchErrorMsg
("NULL_LSN without KnownDeleted", in, lsn, state));
}
/* Ignore a NULL_LSN (return null) if KnownDeleted is set. */
} else {
try {
EnvironmentImpl env = database.getDbEnvironment();
Node node = (Node) env.getLogManager().get(lsn);
node.postFetchInit(database, lsn);
target = node;
if (in != null) {
in.updateMemorySize(null, target);
}
} catch (LogFileNotFoundException LNFE) {
if (!isKnownDeleted() && !isPendingDeleted()) {
throw new DatabaseException
(IN.makeFetchErrorMsg
(LNFE.toString(), in, lsn, state),
LNFE);
}
/* Ignore. Cleaner got to it, so just return null. */
} catch (Exception e) {
throw new DatabaseException
(IN.makeFetchErrorMsg(e.toString(), in, lsn, state),
e);
}
}
}
return target;
}
/*
* Return the state byte for this ChildReference.
*/
byte getState() {
return state;
}
/**
* Return the target for this ChildReference.
*/
public Node getTarget() {
return target;
}
/**
* Sets the target for this ChildReference. No need to make dirty, that
* state only applies to key and LSN.
*/
public void setTarget(Node target) {
this.target = target;
}
/**
* Clear the target for this ChildReference. No need to make dirty, that
* state only applies to key and LSN. This method is public because it's
* safe and used by RecoveryManager. This can't corrupt the tree.
*/
public void clearTarget() {
this.target = null;
}
/**
* Return the LSN for this ChildReference.
*
* @return the LSN for this ChildReference.
*/
public long getLsn() {
return lsn;
}
/**
* Sets the target LSN for this ChildReference.
*
* @param the target LSN.
*/
public void setLsn(long lsn) {
this.lsn = lsn;
state |= DIRTY_BIT;
}
/**
* @return true if the entry has been deleted, although the transaction the
* performed the deletion may not be committed.
*/
private boolean isPendingDeleted() {
return ((state & PENDING_DELETED_BIT) != 0);
}
/**
* @return true if entry is deleted for sure.
*/
public boolean isKnownDeleted() {
return ((state & KNOWN_DELETED_BIT) != 0);
}
/**
* @return true if the object is dirty.
*/
private boolean isDirty() {
return ((state & DIRTY_BIT) != 0);
}
/**
* Get the entry migrate status.
*/
public boolean getMigrate() {
return (state & MIGRATE_BIT) != 0;
}
/**
* Set the entry migrate status.
*/
public void setMigrate(boolean migrate) {
if (migrate) {
state |= MIGRATE_BIT;
} else {
state &= CLEAR_MIGRATE_BIT;
}
}
/*
* Support for logging.
*/
/**
* @see LogWritable#getLogSize
*/
public int getLogSize() {
return
LogUtils.getByteArrayLogSize(key) + // key
LogUtils.getLongLogSize() + // LSN
1; // state
}
/**
* @see LogWritable#writeToLog
*/
public void writeToLog(ByteBuffer logBuffer) {
LogUtils.writeByteArray(logBuffer, key); // key
assert lsn != DbLsn.NULL_LSN;
LogUtils.writeLong(logBuffer, lsn);
logBuffer.put(state); // state
state &= CLEAR_DIRTY_BIT;
}
/**
* @see LogReadable#readFromLog
*/
public void readFromLog(ByteBuffer itemBuffer, byte entryTypeVersion) {
key = LogUtils.readByteArray(itemBuffer); // key
lsn = LogUtils.readLong(itemBuffer); // LSN
state = itemBuffer.get(); // state
state &= CLEAR_DIRTY_BIT;
}
/**
* @see LogReadable#dumpLog
*/
public void dumpLog(StringBuffer sb, boolean verbose) {
sb.append("<ref knownDeleted=\"").append(isKnownDeleted());
sb.append("\" pendingDeleted=\"").append(isPendingDeleted());
sb.append("\">");
sb.append(Key.dumpString(key, 0));
sb.append(DbLsn.toString(lsn));
sb.append("</ref>");
}
/**
* @see LogReadable#logEntryIsTransactional
*/
public boolean logEntryIsTransactional() {
return false;
}
/**
* @see LogReadable#getTransactionId
*/
public long getTransactionId() {
return 0;
}
/*
* Dumping
*/
String dumpString(int nspaces, boolean dumpTags) {
StringBuffer sb = new StringBuffer();
if (lsn == DbLsn.NULL_LSN) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<lsn/>");
} else {
sb.append(DbLsn.dumpString(lsn, nspaces));
}
sb.append('\n');
if (key == null) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<key/>");
} else {
sb.append(Key.dumpString(key, nspaces));
}
sb.append('\n');
if (target == null) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<target/>");
} else {
sb.append(target.dumpString(nspaces, true));
}
sb.append('\n');
sb.append(TreeUtils.indent(nspaces));
sb.append("<knownDeleted val=\"");
sb.append(isKnownDeleted()).append("\"/>");
sb.append("<pendingDeleted val=\"");
sb.append(isPendingDeleted()).append("\"/>");
sb.append("<dirty val=\"").append(isDirty()).append("\"/>");
return sb.toString();
}
public String toString() {
return dumpString(0, false);
}
}